--[[ scheme_type: generic author: Alundaio --]] -------------------------------------- actid = 188115 evaid = 188115 -------------------------------------- local math_random = math.random ------------------------------------------------------------------------------------- -- PRIVATE -------------------------------------------------------------------------------------- local function validate(npc,vid) if (vid and vid < 4294967295 and npc:accessible(vid) and vid ~= npc:level_vertex_id()) then return db.used_level_vertex_ids[vid] == nil or db.used_level_vertex_ids[vid] == npc:id() end return false end local function lmove(npc,vid,st) if (vid == nil or vid >= 4294967295) then return end if (db.used_level_vertex_ids[vid] == npc:id()) then npc:set_dest_level_vertex_id(vid) return vid end if (st.vid) then db.used_level_vertex_ids[st.vid] = nil end if not (npc:accessible(vid)) then local vtemp = vector() vid, vtemp = npc:accessible_nearest(vid and level.vertex_position(vid) or npc:position(), vtemp ) end db.used_level_vertex_ids[vid] = npc:id() st.vid = vid npc:set_dest_level_vertex_id(vid) return vid end local function lmove_ns(npc,vid,st) if (vid == nil or vid >= 4294967295) then return end if (db.used_level_vertex_ids[vid] == npc:id()) then npc:set_dest_level_vertex_id(vid) return vid end if (st.vid) then db.used_level_vertex_ids[st.vid] = nil end if not (npc:accessible(vid)) then local vtemp = vector() vid, vtemp = npc:accessible_nearest(vid and level.vertex_position(vid) or npc:position(), vtemp ) end st.vid = vid npc:set_dest_level_vertex_id(vid) return vid end local function can_cause_panic(npc,cls) local rank = ranks.get_obj_rank_name(npc) local cause_panic_clsids if (rank == "novice") then cause_panic_clsids = { [clsid.bloodsucker_s] = true, [clsid.pseudodog_s] = true, [clsid.burer_s] = true, [clsid.chimera_s] = true, [clsid.controller_s] = true, [clsid.fracture_s] = true, [clsid.poltergeist_s] = true, [clsid.gigant_s] = true, [clsid.snork_s] = true, [clsid.psy_dog_s] = true, [clsid.psy_dog_phantom_s] = true } elseif (rank == "trainee" or rank == "experienced") then cause_panic_clsids = { [clsid.bloodsucker_s] = true, [clsid.burer_s] = true, [clsid.chimera_s] = true, [clsid.controller_s] = true, [clsid.poltergeist_s] = true, [clsid.gigant_s] = true, } end return cause_panic_clsids and cause_panic_clsids[cls] == true end local function find_valid_target(npc,st,bw) --utils_data.debug_write("axr_panic.find valid target") if (st.overflowdetect) then printf("C Stack Overflow Prevention: warning axr_panic scheme making repeated calls without return; save now and reload!") return false end -- Evaluate current target local tg = time_global() local current_target = st.target_id and db.storage[st.target_id] and db.storage[st.target_id].object if (current_target and current_target:alive() and not IsWounded(current_target) and xr_combat_ignore.is_enemy(npc,current_target)) then if (st.__keep_target_until and tg < st.__keep_target_until) then return true end end if not (st.index) then st.index = 1 end if not (st.memory) then st.memory = {} end local size = #st.memory if (size == 0) then local obj,cls -- Create a list of valid targets to iterate through one index per update st.overflowdetect = true for o in npc:memory_visible_objects() do obj = o and o:object() if (obj) then cls = obj.clsid and obj:clsid() if (cls) and (IsStalker(nil,cls) or IsMonster(nil,cls)) and (obj:alive()) then size = size + 1 st.memory[size] = obj:id() end end end st.overflowdetect = nil if (size==0) then return current_target ~= nil end end if (size == 0 or st.index > size) then st.index = 1 iempty_table(st.memory) return current_target ~= nil end local id = st.memory[st.index] st.index = st.index + 1 local target = id and (db.storage[id] and db.storage[id].object) -- Evaluate currently indexed target if (target and target:alive() and npc:relation(target) >= game_object.enemy and not IsWounded(target)) then if (xr_combat_ignore.is_enemy(npc,target) == true) then -- focus on actor if panicked_to_actor is true, which is forced by some events like surrender local cls = target:clsid() if (cls == clsid.script_actor and db.storage[npc:id()].panicked_to_actor) then -- focus on best enemy if they are close by and not a panic source local be = npc:best_enemy() if (be and be:alive() and not IsWounded(be) and not can_cause_panic(npc,be:clsid()) and npc:position():distance_to_sqr(be:position()) <= 30) then st.target_id = be:id() st.__keep_target_until = tg + 8000 return true end if (st.target_id ~= 0) then xr_sound.set_sound_play(npc:id(),"panic_human") end st.__keep_target_until = tg + 5000 st.target_id = target:id() return true end if (can_cause_panic(npc,cls)) then -- focus on best enemy if they are close by and not a panic source local be = npc:best_enemy() if (be and be:alive() and not IsWounded(be) and not can_cause_panic(npc,be:clsid()) and npc:position():distance_to_sqr(be:position()) <= 30) then st.target_id = be:id() st.__keep_target_until = tg + 8000 return true end if (st.target_id ~= target:id() and not st.__played_sound) then xr_sound.set_sound_play(npc:id(),"panic_monster") st.__played_sound = true end st.__keep_target_until = tg + 5000 st.target_id = target:id() return true end end end return false end -------------------------------------------------------------------------------------- -- EVALUATOR PANIC -------------------------------------------------------------------------------------- class "evaluator_stalker_panic" (property_evaluator) function evaluator_stalker_panic:__init(npc,name,storage) super (nil, name) self.st = storage self.st.stage = 0 end function evaluator_stalker_panic:evaluate() --utils_data.debug_write("eva_panic") local npc = self.object if not (npc:alive()) then return false end if (IsWounded(npc)) then return false end local st = db.storage[npc:id()] if (st) and (st.active_scheme == "camper" or st.help_wounded and st.help_wounded.selected_id ~= nil) then return false end local bd = npc:best_danger() if (bd and bd:type() == danger_object.grenade and db.storage[npc:id()].danger_flag) then return false end if (find_valid_target(npc,self.st)) then return true end return false end -------------------------------------------------------------------------------------- -- ACTION PANIC -------------------------------------------------------------------------------------- class "action_stalker_panic" (action_base) function action_stalker_panic:__init (npc,name,storage) super (nil,name) self.st = storage end function action_stalker_panic:initialize() action_base.initialize(self) local npc = self.object npc:set_desired_position() npc:set_desired_direction() db.storage[npc:id()].panicked = true end function action_stalker_panic:try_go_cover(npc,ene_pos) --utils_data.debug_write(strformat("action_stalker_panic:try_go_cover")) local vid = utils_obj.find_close_cover(npc,npc:position(),ene_pos,5,1,90) if (validate(npc,vid)) then return lmove(npc,vid,self.st) end end function action_stalker_panic:try_to_strafe(npc) --utils_data.debug_write(strformat("action_stalker_panic:try_to_strafe")) --utils_obj.debug_nearest(npc,"try_to_strafe") local dir = {} dir[1] = vector_rotate_y(vec_set(npc:direction()),-90) dir[2] = vector_rotate_y(vec_set(npc:direction()),90) local vid local radius local base_point = npc:level_vertex_id() for i=1,2 do radius = 5 while (radius > 1) do vid = level.vertex_in_direction(base_point,dir[i],radius) if (vid and vid < 4294967295 and npc:accessible(vid) and vid ~= npc:level_vertex_id()) then return lmove_ns(npc,vid,self.st) end radius = radius - 1 end end end function action_stalker_panic:try_to_strafe_behind_enemy(npc,enemy,ene_pos,dist) --utils_data.debug_write(strformat("action_stalker_panic:try_to_strafe_behind_enemy")) --utils_obj.debug_nearest(npc,"try_to_strafe_behind_enemy") local dir = vector_rotate_y(vec_set(enemy:direction()),180) local vid local radius = 8 local base_point = npc:level_vertex_id() while (radius > 0) do vid = level.vertex_in_direction(base_point,dir,radius) if (vid and vid < 4294967295 and npc:accessible(vid) and vid ~= npc:level_vertex_id()) then return lmove_ns(npc,vid,self.st) end radius = radius - 1 end end function action_stalker_panic:try_go_backward(npc) --utils_data.debug_write(strformat("action_stalker_panic:try_go_backward")) --utils_obj.debug_nearest(npc,"try_go_backward") local dir = vector_rotate_y(vec_set(npc:direction()),math_random(160,200)) local vid local radius = 8 local base_point = npc:level_vertex_id() while (radius > 0) do vid = level.vertex_in_direction(base_point,dir,radius) if (vid and vid < 4294967295 and npc:accessible(vid) and vid ~= npc:level_vertex_id()) then return lmove_ns(npc,vid,self.st) end radius = radius - 1 end end function action_stalker_panic:try_go_cover_backward(npc,ene_pos) --utils_data.debug_write(strformat("action_stalker_panic:try_go_cover_backward")) --utils_obj.debug_nearest(npc,"try_go_cover_backward") local dir = vector_rotate_y(vec_set(npc:direction()),math_random(160,200)) local vid local radius = 20 local base_point = npc:level_vertex_id() while (radius > 0) do vid = level.vertex_in_direction(base_point,dir,radius) if (validate(npc,vid)) then --return lmove(npc,vid,self.st) break end radius = radius - 1 end if not (vid) then return end local base_position = level.vertex_position(vid) local cover local cover_dist = 30 while not cover and cover_dist > 0 do cover = npc:best_cover(base_position,ene_pos,cover_dist,1,150) if (cover and db.used_level_vertex_ids[cover:level_vertex_id()]) then cover = nil end cover_dist = cover_dist - 1 end vid = cover and cover:level_vertex_id() or vid if (validate(npc,vid)) then return lmove(npc,vid,self.st) end end function action_stalker_panic:execute() --utils_data.debug_write(strformat("action_stalker_panic:execute start")) action_base.execute(self) local npc = self.object local enemy = self.st.target_id and (db.storage[self.st.target_id] and db.storage[self.st.target_id].object) if not (enemy) then self.st.target_id = nil return end if not (enemy:alive()) then self.st.target_id = nil return end --printf("enemy = %s",enemy and enemy:name()) -- ensure and enforce path type if (npc:path_type() ~= game_object.level_path) then npc:set_path_type(game_object.level_path) end local see = npc:see(enemy) local ene_pos = see and utils_obj.safe_bone_pos(enemy,"bip01_spine") or vector():set(npc:memory_position(enemy)) or nil ene_pos.y = ene_pos.y+0.2 local dist = ene_pos:distance_to_sqr(npc:position()) local tg = time_global() if (self.st.vid) then local bw = npc:active_item() if (self.st.__keep_body_state_until==nil or tg > self.st.__keep_body_state_until) then self.st.__keep_body_state_until = tg+5000 local dir = ene_pos and vec_sub(ene_pos,npc:position()) or npc:direction() if (npc:body_state() ~= move.crouch) then --printf("stand=%s",level.high_cover_in_direction(npc:level_vertex_id(),dir)) if (level.high_cover_in_direction(npc:level_vertex_id(),dir) < 0.2) then self.stand = false end else --printf("crouch=%s",level.high_cover_in_direction(npc:level_vertex_id(),dir)) if (level.high_cover_in_direction(npc:level_vertex_id(),dir) >= 0.2) then self.stand = true end end end local new_state -- intentionally use look_position because it's inaccurate if (npc:level_vertex_id() == self.st.vid) then if not (bw) then new_state = "sneak_run_no_wpn" else if (self.stand) then new_state = see and "assault_fire" or "assault" else new_state = see and "sneak_fire" or "sneak_run" end end state_mgr.set_state(npc,new_state,nil,nil,{look_position = ene_pos},{fast_set = true}) self.st.__keep_point_until = self.st.__keep_point_until or tg + math.random(3000,8000) if (self.st.__keep_point_until and tg < self.st.__keep_point_until) then return end self.st.__keep_point_until = nil db.used_level_vertex_ids[self.st.vid] = nil self.st.vid = nil else if not (bw) then new_state = "sneak_run_no_wpn" else if (self.stand) then new_state = see and "assault_fire" or "assault" else new_state = see and "sneak_fire" or "sneak_run" end end state_mgr.set_state(npc,new_state,nil,nil,{look_position = ene_pos},{fast_set = true}) lmove(npc,self.st.vid,self.st) return end end local new_vid = dist < 30 and self:try_go_cover_backward(npc,ene_pos) or self:try_go_cover(npc,ene_pos) if (new_vid) then end --utils_data.debug_write(strformat("action_stalker_panic:execute end 2")) end function action_stalker_panic:finalize() action_base.finalize(self) if (self.st.vid) then db.used_level_vertex_ids[self.st.vid] = nil end db.storage[self.object:id()].panicked = nil end -------------------------------------------------------------------- --------------------------------- BINDER --------------------------- function setup_generic_scheme(npc,ini,scheme,section,stype,temp) local st = xr_logic.assign_storage_and_bind(npc,ini,"stalker_panic",section,temp) end function add_to_binder(npc,ini,scheme,section,storage,temp) if not npc then return end local manager = npc:motivation_action_manager() if not manager then return end if (not npc:alive()) or (character_community(npc) == "monolith" or character_community(npc) == "zombied" or npc:section() == "actor_visual_stalker") then manager:add_evaluator(evaid,property_evaluator_const(false)) temp.needs_configured = false return end local evaluator = evaluator_stalker_panic(npc,"eva_stalker_panic",storage) temp.action = action_stalker_panic(npc,"act_stalker_panic",storage) if not evaluator or not temp.action then return end manager:add_evaluator(evaid,evaluator) temp.action:add_precondition(world_property(stalker_ids.property_alive,true)) temp.action:add_precondition(world_property(stalker_ids.property_danger, false)) temp.action:add_precondition(world_property(evaid,true)) temp.action:add_effect(world_property(evaid,false)) manager:add_action(actid,temp.action) --xr_logic.subscribe_action_for_events(npc, storage, temp.action) end function configure_actions(npc,ini,scheme,section,stype,temp) if not npc then return end local manager = npc:motivation_action_manager() if not manager or not temp.action then return end temp.action:add_precondition(world_property(xr_evaluators_id.sidor_wounded_base,false)) temp.action:add_precondition(world_property(xr_evaluators_id.wounded_exist,false)) if (_G.schemes["rx_ff"]) then temp.action:add_precondition(world_property(rx_ff.evaid,false)) end if (_G.schemes["gl"]) then temp.action:add_precondition(world_property(rx_gl.evid_gl_reload,false)) end if (_G.schemes["facer"]) then temp.action:add_precondition(world_property(xrs_facer.evid_facer,false)) temp.action:add_precondition(world_property(xrs_facer.evid_steal_up_facer,false)) end local action local p = {xr_danger.actid, stalker_ids.action_combat_planner, stalker_ids.action_danger_planner, xr_actions_id.state_mgr + 2, xr_actions_id.alife} for i=1,#p do --printf("ACTION_ALIFE_ID(axr_stalker_panic.configure_actions): " .. tostring(p[i])) action = manager:action(p[i]) if (action) then action:add_precondition(world_property(evaid,false)) else printf("axr_panic: no action id p[%s]",i) end end end function disable_generic_scheme(npc,scheme,stype) local st = db.storage[npc:id()][scheme] if st then st.enabled = false end end function npc_add_precondition(action) if not action then return end action:add_precondition(world_property(evaid,false)) end